#ifndef _SERVER_HPP
#define _SERVER_HPP
#include <boost/asio/detail/chrono.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/system/error_code.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/signal_set.hpp>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <iostream>
#include <memory>
#include <vector>
#include "NetworkThread.hpp"

template <class NetworkThreadType>
class Server
{
public:
    Server(uint16_t port, uint8_t threadNumber) : _acceptor(nullptr), _updateTimer(nullptr), _threadNumber(threadNumber), _port(port), _stoped(false), _prepareStopCallback(nullptr){}
    virtual ~Server()
    {
        if(_signal)
        {
            delete _signal;
            _signal = nullptr;
        }

        if(_acceptor)
        {
            delete _acceptor;
            _acceptor = nullptr;
        }

        if(_updateTimer)
        {
            delete _updateTimer;
            _updateTimer = nullptr;
        }

        for(uint8_t i = 0; i < _threads.size(); ++i)
        {
            delete _threads[i];
            _threads[i] = nullptr;
        }
    }
public:
    void Start()
    {
        _signal = new boost::asio::signal_set(_io_context, SIGINT, SIGTERM);
        _acceptor = new boost::asio::ip::tcp::acceptor(_io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), _port));
        _updateTimer = new boost::asio::steady_timer(_io_context, boost::asio::chrono::milliseconds(1));
        for(uint8_t i = 0; i < _threadNumber; ++i)
        {
            NetworkThreadType* t = new NetworkThreadType();
            t->Start();
            _threads.push_back(t);
        }
        // signal
        StartSignal();

        // tick
        Update();

        // accept
        StartAccept();

        // io run
        _io_context.run();

        // end
        std::cout << "Server Stop ..." << std::endl;
    }

    void Stop()
    {
        if(_stoped) return;

        for(uint8_t i = 0; i < _threads.size(); ++i)
        {
            _threads[i]->Stop();
        }

        _stoped = true;

        _io_context.stop();
    }

    void SetPrepareStopCallback(std::function<void()> cb)
    {
        _prepareStopCallback = cb;
    }
private:
    void StartSignal()
    {
        _signal->async_wait([this](const boost::system::error_code& ec, int signal_number){
            this->SignalCallback(signal_number);
        });
    }

    void Update()
    {
        _updateTimer->expires_after(boost::asio::chrono::milliseconds(1));
        _updateTimer->async_wait([this](boost::system::error_code const&){
            if(!_stoped)
            {
                this->Update();
            }
        });
    }

    void StartAccept()
    {
        uint8_t min_load_thread_index = [this](){
            uint8_t thread_index = 0;
            uint32_t min_load = INT_MAX;
            for(uint8_t i = 0; i < _threads.size(); ++i)
            {
                if(_threads[i]->GetNetWorkLoad() < min_load)
                {
                    min_load = _threads[i]->GetNetWorkLoad();
                    thread_index = i;
                }
            }
            return thread_index;
        }();

        NetworkThreadType* th = _threads[min_load_thread_index];
        assert(th);

        tcp::socket& sc = th->GetSocket();
        _acceptor->async_accept(sc, [this, min_load_thread_index](boost::system::error_code const& ec){
            if(ec)
            {
                std::cout << "accept error" << std::endl;
            }
            else 
            {
                NetworkThreadType* th = this->_threads[min_load_thread_index];
                th->GetSocket().non_blocking(true);
                boost::system::error_code err;
                th->GetSocket().set_option(boost::asio::ip::tcp::no_delay(true), err);
                assert(!err);
                th->AddSocket(std::move(th->GetSocket()));
                std::cout << "accept new connect" << std::endl;
            }

            if(!_stoped)
            {
                this->StartAccept();
            }
        });
    }

    void SignalCallback(int signal_number)
    {
        if(_prepareStopCallback)
        {
            _prepareStopCallback();
        }
        Stop();
    }
private:
    boost::asio::io_context _io_context;
    boost::asio::ip::tcp::acceptor* _acceptor;
    boost::asio::steady_timer* _updateTimer;
    boost::asio::signal_set* _signal;
    std::vector<NetworkThreadType*> _threads;
    uint8_t _threadNumber;
    uint16_t _port;
    bool _stoped;
    std::function<void()> _prepareStopCallback;
};

#endif